Repository Public
1// Auteur : Simon-Pierre Boucher — contact@spboucher.ai2// /api/admin/mail/attachments/[id] — pièce jointe d'un courriel entrant.3// Mêmes règles que le message : propriétaire de la boîte (ou super admin4// pour les non-routés), fichiers hors de /public, nom aléatoire.5import { NextRequest, NextResponse } from "next/server";6import fs from "fs/promises";7import path from "path";8import { getAdminSession } from "@/lib/comms/admin-auth";9import { findMail, canAccessMail, findMailAttachment } from "@/lib/comms/mail";1011const MAIL_UPLOAD_DIR = path.join(process.cwd(), "data", "mail-uploads");1213export async function GET(14 _req: NextRequest,15 { params }: { params: Promise<{ id: string }> },16) {17 const session = await getAdminSession();18 if (!session)19 return NextResponse.json({ error: "Non autorisé." }, { status: 401 });20 const id = Number((await params).id);21 const att = Number.isInteger(id) ? findMailAttachment(id) : undefined;22 const mail = att ? findMail(att.message_id) : undefined;23 if (!att || !mail || !canAccessMail(mail, session.id, session.roles.includes("super_admin")))24 return NextResponse.json({ error: "Fichier introuvable." }, { status: 404 });2526 const safe = path.normalize(att.stored_name).replace(/^(\.\.[/\\])+/, "");27 const full = path.join(MAIL_UPLOAD_DIR, safe);28 if (!full.startsWith(MAIL_UPLOAD_DIR))29 return NextResponse.json({ error: "Chemin refusé." }, { status: 400 });30 let buf: Buffer;31 try {32 buf = await fs.readFile(full);33 } catch {34 return NextResponse.json({ error: "Fichier manquant sur le disque." }, { status: 404 });35 }36 return new NextResponse(new Uint8Array(buf), {37 headers: {38 "Content-Type": att.mime,39 "Content-Disposition": `attachment; filename="${att.original_name.replace(/["\r\n]/g, "")}"`,40 "Cache-Control": "private, no-store",41 },42 });43}44